package CryptoPrim;

import Utils.ByteTree;
import ArithmeticObjects.*;
import ArithUtils.Array;
import ArithUtils.BigNumber;

/**
 * A class which represents a Ciphertext Group (Cw)
 * 
 * @author Itamar Carmel, nirhagin
 */
public class CiphertextGroup {
	
	private VerfGroup Gq; // group of elements of Cw (Cw = Gq^w x Gq^w)
	private int width; // width of the group (w)
	
	/**
	 * Constructor for Ciphertext Group from given group of elements (Gq) and
	 * the width (w) of elements.
	 * 
	 * @param Gq
	 * @param width
	 */
	public CiphertextGroup(VerfGroup Gq, int width) {
		this.Gq = Gq;
		this.width = width;
	}
	
	/**
	 * A method which compares two ciphertext of this width and Gq elements.
	 * The compare method operate coordinate by coordinate.
	 * 
	 * @param ct1
	 * @param ct2
	 * @return false - CT's are not equal | CT's not the same width; true - otherwise
	 */
	public boolean ciphertextIsEqual(Ciphertext ct1, Ciphertext ct2) {
		if (ct1.getWidth() != ct2.getWidth())
			return false;
		for (int i=0; i < this.width; i++){
			if(
					(!Gq.ElementIsEqual(ct1.getA().get(new BigNumber (i)),ct2.getA().get(new BigNumber (i)))) ||
					(!Gq.ElementIsEqual(ct1.getB().get(new BigNumber (i)),ct2.getB().get(new BigNumber (i)))))	
				return false;
		}
		return true;
	}

	/**
	 * Binary operation which returns the exponentiation of given CT and BigNumber. 
	 * 
	 * @param ct
	 * @param e
	 * @return ct^e
	 */
	public Ciphertext pow(Ciphertext ct, BigNumber e) {
		Ciphertext ret = new Ciphertext();
		for (int i=0; i < this.width; i++)
			ret.add(
					Gq.pow(ct.getA().get(i),e),
					Gq.pow(ct.getB().get(i),e));
		return ret;
	}

	/**
	 * Binary operation which returns the multiplication of two CT's.
	 * 
	 * @param ct1
	 * @param ct2
	 * @return ct1 * ct2
	 */
	public Ciphertext multiply(Ciphertext ct1, Ciphertext ct2) {
		Ciphertext ret = new Ciphertext();
		for (int i=0; i < this.width; i++)
			ret.add(
					Gq.mult(ct1.getA().get(i),ct2.getA().get(i)),
					Gq.mult(ct1.getB().get(i),ct2.getB().get(i)));
		return ret;
	}

	/**
	 * Getter for width
	 * 
	 * @return w (width)
	 */
	public int getWidth() {
		return this.width;
	}

	/**
	 * Converts ciphertext to it's BT rep.
	 * 
	 * @param ct
	 * @return node(ct)
	 */
	public ByteTree ciphertextToBT(Ciphertext ct){
		ByteTree bt = ByteTree.generateEmptyNode();
		bt.addMultChildren(Gq.toByteTree(ct.getA()),Gq.toByteTree(ct.getB()));
		return bt;
	}

	/**
	 * Converts BT to it's ciphertext rep.
	 * 
	 * @param bt
	 * @return ciphertext(bt)
	 */
	public Ciphertext BTToCiphertext(ByteTree bt){
		Array<VerfGroup.VElement> a = new Array<VerfGroup.VElement>();
		Array<VerfGroup.VElement> b = new Array<VerfGroup.VElement>();
		if (this.width == 1){
			a.add(Gq.getElement(bt.getTheNChild(0)));
			b.add(Gq.getElement(bt.getTheNChild(1)));
		} else{
			a = Gq.getArrayOfElementsFromBT(bt.getTheNChild(0));
			b = Gq.getArrayOfElementsFromBT(bt.getTheNChild(1));
		}
		return new Ciphertext(a,b);
	}
	
	/**
	 * Converts array of ciphertext to it's BT rep.
	 * according to the algorithm defined in the document.
	 * 
	 * @param bt
	 * @return arrayCiphertexts(bt)
	 */
	public Array<Ciphertext> BTtoCiphertextArray(ByteTree bt) {
		Array<Ciphertext> ret = new Array<Ciphertext>();
		Array<VerfGroup.VElement> temp1;
		Array<VerfGroup.VElement> temp2;
		if (this.getWidth() == 1){
			int size = bt.getTheNChild(0).getAmountOfDirectChildren();
			for(int i=0; i<size; i++){
				temp1 = new Array<VerfGroup.VElement>();
				temp2 = new Array<VerfGroup.VElement>();
				temp1.add(Gq.getElement(bt.getTheNChild(0).getTheNChild(i)));
				temp2.add(Gq.getElement(bt.getTheNChild(1).getTheNChild(i)));
				ret.add(new Ciphertext(temp1,temp2));
			}
		} else{
			int size = bt.getTheNChild(0).getTheNChild(0).getAmountOfDirectChildren();
			for(int i=0; i<size; i++){
				temp1 = new Array<VerfGroup.VElement>();
				temp2 = new Array<VerfGroup.VElement>();
				for(int j=0; j<bt.getTheNChild(0).getAmountOfDirectChildren(); j++){
					temp1.add(Gq.getElement(
							bt.getTheNChild(0).getTheNChild(j).getTheNChild(i)));
					temp2.add(Gq.getElement(
							bt.getTheNChild(1).getTheNChild(j).getTheNChild(i)));
				}
				ret.add(new Ciphertext(temp1, temp2));
			}
		}
		return ret;
	}

	/**
	 * Converts array of ciphertext to it's BT rep.
	 * according to the algorithm defined in the document.
	 * 
	 * @param ctArr
	 * @return
	 */
	public ByteTree ciphertextArrayToBT(Array<Ciphertext> ctArr) {
		
		ByteTree bt = ByteTree.generateEmptyNode();
		ByteTree temp = ByteTree.generateEmptyNode();
		if (this.width == 1){
			for(Ciphertext ct : ctArr){
				temp.addChild(Gq.toByteTree(ct.getA().get(0)));
			}
			bt.addChild(temp);
			temp = ByteTree.generateEmptyNode();;
			for(Ciphertext ct : ctArr){
				temp.addChild(Gq.toByteTree(ct.getB().get(0)));
			}
			bt.addChild(temp);
		} else{
			ByteTree tmp1 = ByteTree.generateEmptyNode();
			ByteTree tmp2 = ByteTree.generateEmptyNode();
			for (int i=0; i<this.width; i++){
				temp = ByteTree.generateEmptyNode();
				for (Ciphertext ct : ctArr){
					temp.addChild(Gq.toByteTree(ct.getA().get(i)));
				}
				tmp1.addChild(temp);
			}
			for (int i=0; i<this.width; i++){
				temp = ByteTree.generateEmptyNode();
				for (Ciphertext ct : ctArr){
					temp.addChild(Gq.toByteTree(ct.getB().get(i)));
				}
				tmp2.addChild(temp);
			}
			bt.addMultChildren(tmp1,tmp2);
		}
		return bt;
	}
	
}
